package com.fxnotes.database.inmemory;

import com.google.common.base.Predicate;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Collections2;
import com.google.inject.Singleton;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

@Singleton
public class InMemoryDatabase {

    private final ArrayListMultimap<Class<?>, Object> database = ArrayListMultimap.create();
    private static final Logger logger = Logger.getLogger(InMemoryDatabase.class.getName());

    @SuppressWarnings("unchecked") 
    public synchronized <T> List<T> findAll(Class<T> type) {
        return (List<T>) database.get(type);
    }

    public synchronized <T> void store(T object) {
        store(object, true);
    }

    private synchronized <T> void store(T object, boolean doLogging) {
        database.put(object.getClass(), object);
        if (doLogging) {
            logger.log(Level.INFO, "Stored object: {0}", object);
        }
    }

    public synchronized <T> void update(T object) {
        remove(object, false);
        store(object, false);
        logger.log(Level.INFO, "Update object: {0}", object);
    }

    public synchronized <T> void remove(T object) {
        remove(object, true);
    }

    private synchronized <T> void remove(T object, boolean doLogging) {
        database.remove(object.getClass(), object);
        if (doLogging) {
            logger.log(Level.INFO, "Removed object: {0}", object);
        }
    }

    public synchronized <T> Collection<T> find(Class<T> type, final Query<T> query) {
        return Collections2.filter(findAll(type), new Predicate<T>() {

            @Override
            public boolean apply(T object) {
                return query.match(object);
            }
        });
    }

    public synchronized <T> T findUnique(Class<T> type, final Query<T> query) {
        Collection<T> result = find(type, query);
        switch (result.size()) {
            case 0:
                logger.log(Level.SEVERE, "Object not found in db!");
                return null;

            case 1:
                return result.iterator().next();

            default:
                logger.log(Level.SEVERE, "Too many objects found in db!");
                return null;
        }
    }

    public synchronized void clear() {
        database.clear();
    }
}
